package uz.rt.dicom.data.element;

import uz.rt.dicom.Padding;
import uz.rt.dicom.ValueRepresentation;
import uz.rt.dicom.data.ComparableList;
import uz.rt.dicom.data.StringElement;
import uz.rt.dicom.io.DicomIOException;
import uz.rt.dicom.io.DicomInputStream;
import uz.rt.dicom.tag.DicomTag;

import java.io.IOException;

public class Time extends StringElement implements Comparable<Time>
{
// ------------------------------ FIELDS ------------------------------

    private TimeGroup[] timeGroup = null;

// --------------------------- CONSTRUCTORS ---------------------------

    public Time(DicomTag tag)
    {
        super(tag);
        setMaxLength(16);
        setPadding(Padding.TRAILING_SPACE);
    }

// --------------------- GETTER / SETTER METHODS ---------------------

    public TimeGroup[] getTimeGroup()
    {
        return timeGroup;
    }

    public void setTimeGroup(TimeGroup[] timeGroup)
    {
        this.timeGroup = timeGroup;
    }

// ------------------------ INTERFACE METHODS ------------------------


// --------------------- Interface Comparable ---------------------

    /**
     * The function compares only the following parameters in order:
     * <ul>
     * <li>Dicom tag</li>
     * <li>Value multiplicity</li>
     * <li>Comparable list of data fields</li>
     * </ul>
     *
     * @param o Time
     * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
     *         the specified object.
     */
    public int compareTo(Time o)
    {
        if (!tag.equals(o.getTag())) return tag.compareTo(o.getTag());
        if (!vm.equals(o.getVm())) return vm.compareTo(o.getVm());
        return value.compareTo(o.getValue());
    }

// --------------------- Interface IDataElement ---------------------

    @Override
    public byte[] getVR()
    {
        return ValueRepresentation.TM;
    }

    @Override
    public ValueRepresentation getValueRepresentation()
    {
        return ValueRepresentation.Time;
    }

    public void read(DicomInputStream dicomInputStream, int length) throws IOException
    {
        value = null;
        value = new ComparableList<String>();
        String s = dicomInputStream.readString(length);
        String[] groups = splitValues(s);
        if (groups.length < vm.getMin() || groups.length > vm.getMax())
        {
            value = null;
            throw new DicomIOException(
                    "Value multiplicity violation: group length = " + groups.length +
                            ", limits are [" + vm.toString() + "]");
        }
        char c;
        timeGroup = new TimeGroup[groups.length];
        int index = 0;
        for (String group : groups)
        {
            for (int i = 0; i < group.length(); i++)
            {
                c = group.charAt(i);
                if (!Character.isDigit(c) && c != ' ' && c != '.')
                {
                    value = null;
                    throw new DicomIOException(
                            "Character \"" + c + " \" is not allow. Allowed characters are: [0-9], '.', ' '");
                }
            }
            if (timeGroup[index] == null) timeGroup[index] = new TimeGroup();
            if (group.length() >= 2)
            {
                timeGroup[index].hour = Integer.valueOf(group.substring(0, 2));
                timeGroup[index].minutes = 0;
                timeGroup[index].seconds = 0;
                timeGroup[index].fractionalSeconds = 0;
            }
            if (group.length() >= 4)
            {
                timeGroup[index].minutes = Integer.valueOf(group.substring(2, 4));
                timeGroup[index].seconds = 0;
                timeGroup[index].fractionalSeconds = 0;
            }
            if (group.length() >= 6)
            {
                timeGroup[index].seconds = Integer.valueOf(group.substring(4, 6));
                timeGroup[index].fractionalSeconds = 0;
            }
            if (group.length() >= 8)
            {
                if (group.charAt(6) == '.')
                {
                    StringBuilder sb = new StringBuilder("0.");
                    int i = 7;
                    while (i < group.length())
                    {
                        sb.append(group.charAt(i));
                        i++;
                    }
                    timeGroup[index].fractionalSeconds = Double.valueOf(sb.toString());
                }
            }
            value.add(group);
            index++;
        }
    }

// -------------------------- OTHER METHODS --------------------------

    /**
     * Get the fractional seconds based on the parsed dicom input stream.
     *
     * @param index Index of the value that needs to be accessed. Value should be with the constraints of the value
     *              multiplicity.
     * @return Fractional seconds
     */
    public double getFractionalSeconds(int index)
    {
        return timeGroup[index].fractionalSeconds;
    }

    /**
     * Get the hour based on the parsed dicom input stream.
     *
     * @param index Index of the value that needs to be accessed. Value should be with the constraints of the value
     *              multiplicity.
     * @return Hour
     */
    public int getHour(int index)
    {
        return timeGroup[index].hour;
    }

    /**
     * Get the minutes based on the parsed dicom input stream.
     *
     * @param index Index of the value that needs to be accessed. Value should be with the constraints of the value
     *              multiplicity.
     * @return Minute
     */
    public int getMinutes(int index)
    {
        return timeGroup[index].minutes;
    }

    /**
     * Get the seconds based on the parsed dicom input stream.
     *
     * @param index Index of the value that needs to be accessed. Value should be with the constraints of the value
     *              multiplicity.
     * @return Seconds
     */
    public int getSeconds(int index)
    {
        return timeGroup[index].seconds;
    }

// -------------------------- INNER CLASSES --------------------------

    /** A time group provides a group in which a number of time parameters are defined. */
    public class TimeGroup implements Comparable<TimeGroup>
    {
        /** Time format */
        private final String format = "HHMMSS.FFFFFF";
        /** Hour of the day */
        private int hour = Integer.MIN_VALUE;
        /** Minutes */
        private int minutes = Integer.MIN_VALUE;
        /** Seconds */
        private int seconds = Integer.MIN_VALUE;
        /** Fractional seconds */
        private double fractionalSeconds = Integer.MIN_VALUE;

        public int compareTo(TimeGroup o)
        {
            if (hour != o.hour) return Integer.compare(hour, o.hour);
            if (minutes != o.minutes) return Integer.compare(minutes, o.minutes);
            if (seconds != o.seconds) return Integer.compare(seconds, o.seconds);
            if (fractionalSeconds != o.fractionalSeconds)
                return Double.compare(fractionalSeconds, o.fractionalSeconds);
            return 0;
        }

        @Override
        public boolean equals(Object o)
        {
            if (this == o) return true;
            if (!(o instanceof TimeGroup)) return false;

            TimeGroup timeGroup = (TimeGroup) o;

            return fractionalSeconds == timeGroup.fractionalSeconds && hour == timeGroup.hour && minutes == timeGroup.minutes && seconds == timeGroup.seconds && format
                    .equals(timeGroup.format);
        }

        @Override
        public int hashCode()
        {
            int result;
            long temp;
            result = format.hashCode();
            result = 31 * result + hour;
            result = 31 * result + minutes;
            result = 31 * result + seconds;
            temp = fractionalSeconds != +0.0d ? Double.doubleToLongBits(fractionalSeconds) : 0L;
            result = 31 * result + (int) (temp ^ (temp >>> 32));
            return result;
        }

        /**
         * Get the hour in the time group.
         *
         * @return Hour of the time group
         */
        public int getHour()
        {
            return hour;
        }

        /**
         * Set the hour in the time group.
         *
         * @param hour Hour of the time group
         */
        public void setHour(int hour)
        {
            this.hour = hour;
        }

        /**
         * Get the minutes in the time group.
         *
         * @return Minutes of the time group
         */
        public int getMinutes()
        {
            return minutes;
        }

        /**
         * Set the minutes in the time group.
         *
         * @param minutes Minutes of the time group
         */
        public void setMinutes(int minutes)
        {
            this.minutes = minutes;
        }

        /**
         * Get the seconds in the time group.
         *
         * @return Seconds of the time group
         */
        public int getSeconds()
        {
            return seconds;
        }

        /**
         * Set the seconds in the time group.
         *
         * @param seconds Seconds of the time group
         */
        public void setSeconds(int seconds)
        {
            this.seconds = seconds;
        }

        /**
         * Get the fractional seconds in the time group.
         *
         * @return Fractional seconds of the time group
         */
        public double getFractionalSeconds()
        {
            return fractionalSeconds;
        }

        /**
         * Set the fractional seconds in the time group.
         *
         * @param fractionalSeconds Fractional seconds of the time group
         */
        public void setFractionalSeconds(double fractionalSeconds)
        {
            this.fractionalSeconds = fractionalSeconds;
        }
    }
}
